1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 /**
12 *    This file provides the essential information for specifying vertices
13 *   for the target 3D API. Its Attributes/Layout, some preset layouts.
14 *    The workflow for vertices are entirely based on OpenGL, using VAOs and VBOs
15 *
16 */
17 
18 module hip.hiprenderer.vertex;
19 import hip.hiprenderer.renderer;
20 import hip.error.handler;
21 import hip.console.log;
22 public import hip.api.renderer.vertex;
23 
24 
25 
26 
27 private __gshared HipVertexArrayObject lastBoundVertex;
28 
29 /**
30 *   For using this class, you must first define the vertex layout for after that, create the vertex
31 *   buffer and/or the index buffer.
32 */
33 class HipVertexArrayObject
34 {
35     IHipVertexArrayImpl VAO;
36     IHipRendererBuffer  VBO;
37     IHipRendererBuffer  EBO;
38     ///Accumulated size of the vertex data
39     uint stride;
40     ///How many data slots it uses, for instance, vec3 will count +3
41     uint dataCount;
42     HipVertexAttributeInfo[] infos;
43 
44     bool isBonded;
45     protected bool hasVertexInitialized;
46     protected bool hasIndexInitialized;
47 
48     /**
49     *   Remember calling sendAttributes
50     */
51     this()
52     {
53         isBonded = false;
54         this.VAO = HipRenderer.createVertexArray();
55     }
56 
57     /**
58     *   Creates and binds an index buffer.
59     */
60     void createIndexBuffer(index_t count, HipResourceUsage usage)
61     {
62         assert(EBO is null, "Can't create buffer if it is already assigned.");
63         this.EBO = HipRenderer.createBuffer(count*index_t.sizeof, usage, HipRendererBufferType.index);
64     }
65     /**
66     *   Sets the index buffer. Mainly useful for sharing multiple index buffer (quads and etc)
67     */
68     void setIndexBuffer(IHipRendererBuffer buffer)
69     {
70         this.EBO = buffer;
71     }
72 
73     /**
74     * Creates and binds a vertex buffer.
75     *
76     * The vertex buffer size is dependant on the attributes that were appended to this vertex array.
77     */
78     void createVertexBuffer(uint count, HipResourceUsage usage)
79     {
80         this.VBO = HipRenderer.createBuffer(count*this.stride, usage, HipRendererBufferType.vertex);
81     }
82     /**
83     *   This function creates an attribute information,
84     * for later sending it(it is necessary as the stride needs to be recalculated)
85     */
86     HipVertexArrayObject appendAttribute(
87         uint count,
88         HipAttributeType valueType,
89         uint typeSize,
90         string infoName,
91         bool isPadding = false,
92     )
93     {
94         HipVertexAttributeInfo info = HipVertexAttributeInfo(
95             name: infoName,
96             count: count,
97             valueType: valueType,
98             typeSize: typeSize,
99             index: cast(uint)infos.length,
100             //It actually is the `last stride`, which is the same as the offset is the total current stride
101             offset: stride
102         );
103 
104         info.offset = stride;
105         // if(!isPadding)
106         {
107             infos~= info;
108             dataCount+= count;
109         }
110         stride+= count*typeSize;
111         return this;
112     }
113 
114     HipVertexArrayObject appendAttribute(T)(string infoName, bool isPadding = false)
115     {
116         uint count = 1;
117         HipAttributeType type = HipAttributeType.Float;
118         uint typeSize = float.sizeof;
119         import hip.math.vector;
120 
121         static if(is(T == Vector2)) count = 2;
122         else static if(is(T == Vector3)) count = 3;
123         else static if(is(T == Vector4) || is(T == HipColorf)) count = 4;
124         else static if(is(T == HipColor))
125         {
126             type = HipAttributeType.Rgba32;
127             count = 4;
128             typeSize = ubyte.sizeof;
129         }
130         else
131         {
132             static if(is(T == int)) type = HipAttributeType.Int;
133             else static if(is(T == uint)) type = HipAttributeType.Uint;
134             else static if(is(T == bool)) type = HipAttributeType.Bool;
135             else
136                 static assert(is(T == float), "Unrecognized type for attribute: "~T.stringof);
137 
138             typeSize = T.sizeof;
139         }
140         return appendAttribute(count, type, typeSize ,infoName, isPadding);
141     }
142 
143     /**
144     *   Sets the attribute infos that were appended to this object. This function must only be called
145     *   after binding/creating a VBO, or it will fail
146     */
147     void sendAttributes(Shader s)
148     {
149         // if(!isBonded)
150         // {
151         //     ErrorHandler.showErrorMessage("VertexArrayObject error", "VAO wasn't bound when trying to send its attributes");
152         //     return;
153         // }
154         this.VAO.createInputLayout(VBO, EBO, infos, stride, s.vertexShader, s.shaderProgram);
155     }
156 
157     void bind()
158     {
159         static if(UseDelayedUnbinding)
160         {
161             if(lastBoundVertex is this)
162                 return;
163             if(lastBoundVertex !is null)
164             {
165                 lastBoundVertex.isBonded = false;
166                 lastBoundVertex.VAO.unbind(lastBoundVertex.VBO, lastBoundVertex.EBO);
167             }
168             lastBoundVertex = this;
169         }
170         if(!this.isBonded)
171         {
172             isBonded = true;
173             this.VAO.bind(this.VBO, this.EBO);
174         }
175         else assert(false, "Erroneous bind.");
176     }
177     void unbind()
178     {
179         static if(UseDelayedUnbinding)
180             return;
181         if(this.isBonded)
182         {
183             isBonded = false;
184             this.VAO.unbind(this.VBO, this.EBO);
185         }
186         else assert(false, "Erroneous unbind.");
187     }
188 
189     /**
190     *   Sets the VBO data. Use this function only for initialization as it allocates memory.
191     *
192     *   If you wish to only update its data, call updateVertices instead.
193     */
194     void setVertices(const void[] data)
195     {
196         if(VBO is null)
197             ErrorHandler.showErrorMessage("Null VertexBuffer", "No vertex buffer was created before setting its vertices");
198         else
199         {
200             hasVertexInitialized = true;
201             this.VBO.setData(data);
202         }
203     }
204     /**
205      * Update the VBO. Won't cause memory allocation.
206      * Params:
207      *   count = How many vertices to update
208      *   data = The data containing a type which is conforming to the VAO.
209      *   offset = The offset is always multiplied by this vertex array object stride.
210      */
211     void updateVertices(const void[] data, int offset = 0)
212     {
213         if(VBO is null)
214             ErrorHandler.showErrorMessage("Null VertexBuffer", "No vertex buffer was created before setting its vertices");
215         ErrorHandler.assertExit(hasVertexInitialized, "Vertex must setData before updating its contents.");
216         this.VBO.updateData(offset*this.stride, data);
217     }
218     /**
219     *   Will set the indices data. Beware that this function may allocate memory.
220     *
221     *   If you need to only change its data value instead of allocating memory for a greater index buffer
222     *   call updateIndices
223     */
224     void setIndices(const index_t[] data)
225     {
226         if(EBO is null)
227             ErrorHandler.showErrorMessage("Null IndexBuffer", "No index buffer was created before setting its indices");
228         else
229         {
230             hasIndexInitialized = true;
231             this.EBO.setData(data);
232         }
233     }
234     /**
235     *   Updates the index buffer's data. It won't allocate memory
236     */
237     void updateIndices(const index_t[] data, int offset = 0)
238     {
239         if(EBO is null)
240             ErrorHandler.showErrorMessage("Null IndexBuffer", "No index buffer was created before setting its indices");
241         else
242         {
243             ErrorHandler.assertExit(hasIndexInitialized, "Index must setData before updating its contents.");
244             this.EBO.updateData(cast(int)(offset*index_t.sizeof), data);
245         }
246     }
247 
248     /**
249     * Receives a struct and creates a VAO based on its member types and names.
250     */
251     static HipVertexArrayObject getVAO(T)() if(is(T == struct))
252     {
253         import std.traits:isFunction;
254         import hip.util.reflection:hasUDA;
255 
256         HipVertexArrayObject obj = new HipVertexArrayObject();
257         static foreach(member; __traits(allMembers, T))
258         {{
259             alias mem = __traits(getMember, T, member);
260             static if(!isFunction!(mem) && __traits(compiles, mem.offsetof))
261             {
262                 obj.appendAttribute!((typeof(mem)))
263                 (
264                     member,
265                     hasUDA!(mem, HipShaderInputPadding)
266                 );
267             }
268         }}
269         return obj;
270     }
271 
272 }